#ifndef OHOS_FILE_ASSET_LOADER_HPP
#define OHOS_FILE_ASSET_LOADER_HPP

#include "models/file_asset_loader.h"
#include "helpers/exception_handler.h"
#include "helpers/factories.h"
#include "helpers/general.h"
#include "refs.h"
#include <js_native_api.h>

namespace ohos_rive {
FileAssetLoader::FileAssetLoader(napi_env env, napi_value jsObject)
{
    m_env = env;

    napi_valuetype valueType;
    napi_typeof(env, jsObject, &valueType);
    if (valueType != napi_object) {
        LOGE("Invalid JavaScript object provided");
        return;
    }

    napi_status status = napi_create_reference(env, jsObject, 1, &m_jsFileAssetLoaderRef);
    if (status != napi_ok) {
        LOGE("Failed to create reference");
        return;
    }

    napi_value loadContentsMethod;
    status = napi_get_named_property(env, jsObject, "loadContents", &loadContentsMethod);
    if (status != napi_ok) {
        LOGE("loadContents method not found on ETS object");
        return;
    }

    napi_valuetype methodType;
    napi_typeof(env, loadContentsMethod, &methodType);
    if (methodType != napi_function) {
        LOGE("loadContents is not a function");
        return;
    }

    status = napi_create_reference(env, loadContentsMethod, 1, &m_jsLoadContentsMethodRef);
    if (status != napi_ok) {
        LOGE("Failed to cache loadContents method reference");
        return;
    }

    LOGI("FileAssetLoader successfully initialized with cached method");
    if (!g_RiveFactory) {
        g_RiveFactory = new OhosRiveRenderFactory(env);
    }
}

FileAssetLoader::~FileAssetLoader()
{
    if (m_jsFileAssetLoaderRef) {
        napi_delete_reference(m_env, m_jsFileAssetLoaderRef);
        m_jsFileAssetLoaderRef = nullptr;
    }
    if (m_jsLoadContentsMethodRef) {
        napi_delete_reference(m_env, m_jsLoadContentsMethodRef);
        m_jsLoadContentsMethodRef = nullptr;
    }
}

bool FileAssetLoader::loadContents(rive::FileAsset &asset,
                                   rive::Span<const uint8_t> inBandBytes,
                                   rive::Factory *factory)
{
    assert(m_rendererType != RendererType::None);

    if (!m_jsFileAssetLoaderRef || !m_jsLoadContentsMethodRef) {
        LOGE("JavaScript object or method reference not available");
        return false;
    }

    napi_value jsLoaderObject;
    napi_value loadContentsMethod;
    napi_status status = napi_get_reference_value(m_env, m_jsFileAssetLoaderRef, &jsLoaderObject);
    if (status != napi_ok) {
        LOGE("Failed to get loader object");
        return false;
    }

    status = napi_get_reference_value(m_env, m_jsLoadContentsMethodRef, &loadContentsMethod);
    if (status != napi_ok) {
        LOGE("Failed to get cached loadContents method");
        return false;
    }

    napi_value jsFileAsset = MakeJsAsset(m_env, asset, m_rendererType);
    napi_value jsByteArray = CreateByteArray(m_env, inBandBytes);

    if (!jsFileAsset || !jsByteArray) {
        LOGE("Failed to create method parameters");
        return false;
    }

    napi_value argv[2] = {jsFileAsset, jsByteArray};
    napi_value callResult;
    status = napi_call_function(m_env, jsLoaderObject, loadContentsMethod, 2, argv, &callResult);
    if (status != napi_ok) {
        LOGE("Failed to call loadContents method");
        return false;
    }

    bool result = false;
    status = napi_get_value_bool(m_env, callResult, &result);
    if (status != napi_ok) {
        LOGI("Failed to get boolean result");
        return false;
    }

    LOGI("ETS loadContents returned: %{public}d", result);
    return result;
}

napi_value FileAssetLoader::MakeJsAsset(napi_env env, rive::FileAsset &asset, RendererType rendererType)
{
    const char *className = nullptr;
    if (asset.is<rive::ImageAsset>()) {
        className = "ImageAsset";
        LOGI("Creating ImageAsset for asset: %{public}s", asset.name().c_str());
    } else if (asset.is<rive::FontAsset>()) {
        className = "FontAsset";
        LOGI("Creating FontAsset for asset: %{public}s", asset.name().c_str());
    } else if (asset.is<rive::AudioAsset>()) {
        className = "AudioAsset";
        LOGI("Creating AudioAsset for asset: %{public}s", asset.name().c_str());
    } else {
        LOGE("Unknown file asset type: %{public}d for asset: %{public}s", asset.typeKey, asset.name().c_str());
        return nullptr;
    }

    napi_value assetClass = GetClass(env, className);
    if (!assetClass) {
        LOGE("Failed to get %{public}s class from registry", className);
        return nullptr;
    }

    LOGI("Successfully retrieved %{public}s class from registry", className);

    napi_value constructorArgs[2];
    napi_status status;

    // native pointer (address)
    status = napi_create_int64(env, reinterpret_cast<int64_t>(&asset), &constructorArgs[0]);
    if (status != napi_ok) {
        LOGE("Failed to create pointer argument");
        return nullptr;
    }

    // rendererType
    status = napi_create_int32(env, static_cast<int32_t>(rendererType), &constructorArgs[1]);
    if (status != napi_ok) {
        LOGE("Failed to create rendererType argument");
        return nullptr;
    }

    // asset
    napi_value jsAsset;
    status = napi_new_instance(env, assetClass, 2, constructorArgs, &jsAsset);
    if (status != napi_ok) {
        LOGE("Failed to create %{public}s instance, status: %{public}d", className, status);
        napi_value exception;
        napi_get_and_clear_last_exception(env, &exception);
        if (exception) {
            LOGE("Exception occurred while creating %{public}s instance", className);
        }
        return nullptr;
    }

    LOGI("Successfully created %{public}s instance for asset: %{public}s", className, asset.name().c_str());
    return jsAsset;
}

napi_value FileAssetLoader::CreateByteArray(napi_env env, rive::Span<const uint8_t> data)
{
    if (data.size() == 0) {
        napi_value emptyArray;
        napi_status status = napi_create_typedarray(env, napi_uint8_array, 0, nullptr, 0, &emptyArray);
        if (status != napi_ok) {
            LOGE("Failed to create empty typed array");
            return nullptr;
        }
        return emptyArray;
    }

    napi_value arrayBuffer;
    void *arrayBufferData = nullptr;
    napi_status status = napi_create_arraybuffer(env, data.size(), &arrayBufferData, &arrayBuffer);
    if (status != napi_ok) {
        LOGE("Failed to create ArrayBuffer, status: %{public}d", status);
        return nullptr;
    }

    if (!arrayBufferData) {
        LOGE("ArrayBuffer data is null");
        return nullptr;
    }

    memcpy(arrayBufferData, data.data(), data.size());
    napi_value typedArray;
    status = napi_create_typedarray(env, napi_uint8_array, data.size(), arrayBuffer, 0, &typedArray);
    if (status != napi_ok) {
        LOGE("Failed to create Uint8Array, status: %{public}d", status);
        return nullptr;
    }

    LOGI("Successfully created byte array with size: %{public}zu", data.size());
    return typedArray;
}

napi_env FileAssetLoader::GetEnv() const
{
    return m_env;
}

void FileAssetLoader::setRendererType(RendererType rendererType)
{
    if (m_rendererType != rendererType) {
        m_rendererType = rendererType;
    }
}

RendererType FileAssetLoader::getRendererType() const
{
    return m_rendererType;
}
} // namespace ohos_rive
#endif // OHOS_FILE_ASSET_LOADER_HPP